home *** CD-ROM | disk | FTP | other *** search
/ CD Actual 1 / PC Actual CD 01.iso / share / dos / demos / planets / doc.lzh / PLANET.TXT < prev   
Encoding:
Text File  |  1994-05-01  |  45.1 KB  |  1,075 lines

  1.  
  2.  
  3.  
  4.  
  5.                    The Definitive Guide to Bouncing Planets
  6.                        (a document with a modest title)
  7.  
  8.  
  9.                                   Simon Hern
  10.  
  11.                              22 Harrington Drive
  12.                                Bedford MK41 8DB
  13.                                    England
  14.  
  15.  
  16.           This document describes how to create a computer animation
  17.           of   a   rotating   planet  using  simple  texture-mapping
  18.           techniques. It  should  make  an  interesting  programming
  19.           project  for  someone  with  a basic knowledge of assembly
  20.           language and computer graphics.
  21.  
  22.  
  23.  
  24.  
  25.  
  26.    August, 1992
  27.    Revised December, 1992
  28.    Revised April, 1994
  29.  
  30.  
  31.  
  32.  
  33.  
  34.    1.  Introduction
  35.  
  36.  
  37.    1.1.  Planets, Past and Present
  38.  
  39.         In the spring of 1990 a computer animation  of  a  rotating  planet
  40.    lived  out  a  brief,  anonymous but happy existence. Years have passed.
  41.    Now here's the sequel.
  42.  
  43.         The greatest improvement of this program over the  hacked-together-
  44.    in-Basic  original is the neatness and completeness of its design.  More
  45.    interesting improvements are:
  46.  
  47.         *   Faster animation techniques.
  48.  
  49.         *   Choice of planet sizes and axes of rotation.
  50.  
  51.         *   Generation of semi-convincing planet surfaces.
  52.  
  53.         *   Illumination effects.
  54.  
  55.         The makers of the original 'Star Trek' would've died  to  use  this
  56.    animation as a special effect. That's how good it is.
  57.  
  58.  
  59.    1.2.  Disclaimers and Apologies
  60.  
  61.         There are a few things I ought to mention before going on.
  62.  
  63.         Firstly, I am by  no  stretch  of  the  imagination  an  expert  in
  64.    computer  graphics,  or  for  that  matter  computer  programming of any
  65.    description.  Nearly all of what follows I figured out for myself during
  66.    boring  physics  lessons (something I've always felt quite proud of). If
  67.    there are any books that describe how to do this  sort  of  thing,  I've
  68.    never seen them (though I would certainly like to).
  69.  
  70.         Secondly, a work of stunning originality this isn't. When I started
  71.    this project I had never seen or heard of anything like it. But that was
  72.    probably more by chance than anything else since I've now had reports of
  73.    people  writing  spinning planet animations (along very similar lines to
  74.    what I'm describing here) as far back as the mid-eighties.
  75.  
  76.         Finally, I must apologize for the naming of some  of  the  concepts
  77.    described  here  (Gush,  Complexion,  Riff, etc). They are not technical
  78.    terms, merely the inventions of a warped mind (I was reading  a  lot  of
  79.    Clive Barker books at the time).
  80.  
  81.         That's enough of that. Now on with the show.
  82.  
  83.  
  84.    1.3.  Overview of the Program
  85.  
  86.         The planet program divides up into three  basic  segments  which  I
  87.    will cover (in long-winded detail) in turn.
  88.  
  89.         The first part of the program creates a large array of data  called
  90.    the  Gush  which  is required for the rapid animation of the planet. See
  91.    Section 2.
  92.  
  93.         The second part of the program generates a random surface  for  the
  94.    planet  and  gives  it  colour.  The  resulting data array is called the
  95.    Complexion.  See Section 3.
  96.  
  97.         The third part of the program uses these two data arrays to animate
  98.    the  planet  in real time. For each frame of animation the entire planet
  99.    is redrawn, rotated as required. See Section 5.
  100.  
  101.         Sections 4 and 6 contain  details  on  how  the  animation  can  be
  102.    implemented in practice.
  103.  
  104.  
  105.    1.4.  Planet Shapes and Sizes
  106.  
  107.         The following constant values appear in various  places  throughout
  108.    the program. They control certain aspects of the planet's appearance.
  109.  
  110.    RADIUS  This (integer) value is the radius in pixels of the planet as it
  111.            appears on the screen.
  112.  
  113.    DIST    This is the apparent distance of the observer from  the  planet.
  114.            It  is  measured  in  pixels  from the planet's centre and would
  115.            usually take a value in the order of several times the width  of
  116.            the  screen.  It  must have a value greater than that of RADIUS.
  117.            (The effect this value has is, to be honest, pretty minimal.)
  118.  
  119.    rho     This value (an  integer)  is  the  resolution  of  the  planet's
  120.            surface.   It  is  the  number  of  points of latitude (north to
  121.            south) and half the number of points of  longitude  (around  the
  122.            equator) used when giving coordinates to the landscape.
  123.  
  124.    phi     This is the angle through which the  axis  of  rotation  of  the
  125.            planet  is  tilted  backwards  from the vertical (as seen by the
  126.            observer).
  127.  
  128.    theta   This is the angle through which the  axis  of  rotation  of  the
  129.            planet is turned clockwise (as seen by the observer).
  130.  
  131.         phi and theta do not need to be in any particular units since it is
  132.    their  sines  and cosines that are required and the units conversion can
  133.    take place when these are calculated.
  134.  
  135.         These constants will be discussed in more detail as they are used.
  136.  
  137.  
  138.    2.  Writing the Gush: Data for Spinning a Planet
  139.  
  140.  
  141.    2.1.  From Screen to Surface
  142.  
  143.         A lot of calculations need to be performed before drawing a planet.
  144.    To  speed  up  the  animation  procedure,  the  results of most of these
  145.    calculations are worked out in advance and stored in a data array  which
  146.    I  call  the Gush.  The Gush consists of data for transforming points of
  147.    the planet as seen on the screen to points on the mercator  map  of  the
  148.    planet's surface.
  149.  
  150.         Two stages are  required  to  create  the  Gush.  The  first  stage
  151.    considers  the  Gush  as  a two-dimensional grid (the Square Gush), with
  152.    each point corresponding to a pixel on  the  screen,  and  performs  the
  153.    lengthy  floating-point  calculations.   In the second stage the data in
  154.    the Gush is rearranged  to  make  it  more  readily  accessable  to  the
  155.    animation routines. This second stage Gush is called the Line Gush.
  156.  
  157.  
  158.    2.2.  Constants and the Gush
  159.  
  160.         All of the constants described in Section 1.4 are  referred  to  in
  161.    the Gush calculations.
  162.  
  163.         It is worth  noting  that  the  values  of  DIST,  phi,  theta  and
  164.    (possibly)  RADIUS  are  needed  only  when  creating  the Gush and will
  165.    probably not appear anywhere else in the program. As  a  result,  Gushes
  166.    created   using  different  values  for  these  constants  can  be  used
  167.    interchangeably.
  168.  
  169.  
  170.    2.3.  The Square Gush
  171.  
  172.         The Square Gush is an array of size 2*RADIUS  by  2*RADIUS  and  is
  173.    referenced  by  variables xs and ys, both of which take values from 0 to
  174.    2*RADIUS-1.  Each entry in the array holds two values: xm and  ym,  both
  175.    non-negative integers of size depending on the choice of rho.
  176.  
  177.         The array will be described using the notation  gush[ys][xs].xm_val
  178.    and  gush[ys][xs].ym_val  to mean the values of xm and ym at the (xs,ys)
  179.    position in the array.
  180.  
  181.         The points of the Square Gush correspond directly to the pixels  of
  182.    the  planet's  image on the screen; the empty space around the planet is
  183.    represented by null values in the array.
  184.  
  185.         The values of xm and ym for a point (xs,ys) are calculated  in  the
  186.    following way.
  187.  
  188.         First, define a new constant, g, as
  189.  
  190.             g = DIST * RADIUS / sqrt( DIST*DIST + RADIUS*RADIUS )
  191.                         [sqrt() meaning 'square root']
  192.  
  193.         Next, to make the calculations  faster,  work  out  the  sines  and
  194.    cosines of phi and theta, calling them sph, sth, cph and cth.
  195.  
  196.         For each point (xs,ys) evaluate the following temporary values:
  197.  
  198.                             xg = xs - RADIUS + 0.5
  199.                             yg = ys - RADIUS + 0.5
  200.  
  201.                           j = sqrt( xg*xg + yg*yg )
  202.  
  203.         If j is greater than RADIUS then (xs,ys) is  not  a  point  in  the
  204.    planet's  image  and  should be ignored; set xm = ym = -1 (or some other
  205.    null value). Otherwise,
  206.  
  207.               h = sqrt( DIST*DIST * ( g*g - j*j ) + g*g * j*j )
  208.                  k = ( DIST*DIST - h ) / ( DIST*DIST + j*j )
  209.  
  210.                                  xp = k * xg
  211.                                  yp = k * yg
  212.                        zp = sqrt( g*g - xp*xp - yp*yp )
  213.  
  214.         (The  vector  (xp,yp,zp),  called  P,  can  be  used  to  calculate
  215.    illumination effects. See Section 6.2.)
  216.  
  217.                     xt = xp*cth - yp*sth*cph + zp*sth*sph
  218.                     yt = xp*sth + yp*cth*cph - zp*cth*sph
  219.                              zt = yp*sph + zp*cph
  220.  
  221.                            w = sqrt( g*g - yt*yt )
  222.                             alpha = acos( yt / g )
  223.                            beta = acos( - xt / w )
  224.                       [acos() meaning 'inverse cosine']
  225.  
  226.         All of these calculations use floating-point arithmetic. xm and ym,
  227.    on the other hand, are integers, and when converting from floating-point
  228.    to integer values in the next set of calculations the 'integer part'  of
  229.    the  number (the largest integer not greater than that number) should be
  230.    taken.
  231.  
  232.                           xm = (int) beta * rho / pi
  233.                          ym = (int) alpha * rho / pi
  234.  
  235.           [pi is (predictably enough) the constant 3.14159265(etc).
  236.          All the trigonometric calculations here operate in radians.]
  237.  
  238.                       if xm = rho then let xm = rho - 1
  239.                       if ym = rho then let ym = rho - 1
  240.                     if zt < 0 then let xm = 2*rho - 1 - xm
  241.  
  242.         xm takes values from 0 to 2*rho-1 and ym takes  values  from  0  to
  243.    rho-1.   These are the coordinates of a point on the mercator map of the
  244.    planet's surface.
  245.  
  246.         The values of xm and ym can now be stored in the Gush array.
  247.  
  248.                            gush[ys][xs].xm_val = xm
  249.                            gush[ys][xs].ym_val = ym
  250.  
  251.  
  252.    2.4.  The Line Gush
  253.  
  254.         The Line Gush is all of the important data  from  the  Square  Gush
  255.    rearranged  in  such  a  way  as  to make the final animation as fast as
  256.    possible (this being where the name 'Gush' comes in).
  257.  
  258.         The Line Gush takes the form of a one-dimensional array,  a  string
  259.    of values in the order they are used to draw the planet.
  260.  
  261.         The actual format of the Line Gush is determined by  the  procedure
  262.    used  to  display  the  planet. Here is an example Line Gush format. For
  263.    more details see Sections 4 and 5.
  264.  
  265.         The first entry in the Line Gush is the number of horizontal  lines
  266.    needed  to  draw the planet, i.e., the planet's height in pixels.  It is
  267.    equal to 2*RADIUS.
  268.  
  269.         Since the planet is circular, these horizontal  lines  will  be  of
  270.    different lengths.  The Square Gush treats all the lines as being of the
  271.    same length and fills in the gaps with 'null' entries.  These nulls  are
  272.    missed out when putting together the Line Gush.
  273.  
  274.         Now, for each horizontal line of the planet (corresponding to  each
  275.    value taken by ys), the following values are stored in the Line Gush:
  276.  
  277.    (1)  The relative screen position of the start of the line.   Since  the
  278.         lines   are  all  of  different  lengths,  the  Gush  must  provide
  279.         information on how to arrange them to give a circular  shape.  This
  280.         information will probably take the form of an amount to be added to
  281.         the screen address of the last point plotted on the  previous  line
  282.         to give the address of the first point to plot on this new line.
  283.  
  284.    (2)  A function of the length of the line;  this  value  determines  the
  285.         number  of points to plot. (For speed of animation, it may be other
  286.         than simply the number of points on the line.)
  287.  
  288.    (3)  For each point on the line, that point's xm and ym values (possibly
  289.         combined into a single value).
  290.  
  291.         It takes a considerable amount of time to create a full (Line) Gush
  292.    so  it  is  advisable  to save the completed data to disk rather than to
  293.    create new data each time.
  294.  
  295.         There is no simple formula for the size of the Line  Gush,  but  in
  296.    the above example it will be smaller than the Square Gush which consists
  297.    of 2*4*RADIUS*RADIUS bytes (or words, depending on how  xm  and  ym  are
  298.    stored).
  299.  
  300.         An alternative approach is to create a Compiled Gush, an executable
  301.    mixture  of  data and machine code which allows for very fast animation,
  302.    but which takes up a large amount of memory.
  303.  
  304.  
  305.    3.  Third Day Song
  306.  
  307.  
  308.    3.1.  Making Planets
  309.  
  310.         This part of the  program  creates  a  random,  vaguely  realistic-
  311.    looking  planet surface which is something like fractally generated. (In
  312.    my brief search around the library I couldn't find any  (comprehensible)
  313.    information about generating realistic spherical planet surfaces.)
  314.  
  315.         The method used is as follows. The planet's surface (a  sphere)  is
  316.    cut around a random equator into two hemispheres. One of the hemispheres
  317.    is selected and the altitudes of all points on it increased or decreased
  318.    relative  to  the other hemisphere. This is repeated many times with the
  319.    relative changes in altitude gradually reducing in significance.
  320.  
  321.         This process acts on an array of altitude values called  the  Skin,
  322.    which  describes  in a two dimensional mercator map the spherical planet
  323.    surface.  When the Skin is complete it is transformed into an  array  of
  324.    colour  values  called  the  Complexion. It is this which is used in the
  325.    animation.
  326.  
  327.  
  328.    3.2.  The Skin
  329.  
  330.         The Skin is a two-dimensional array of  size  rho  by  2*rho  (this
  331.    being  the definition of the value rho), and each entry in it represents
  332.    the altitude of a point on the surface of the planet.  The Skin will  be
  333.    referred  to  by  skin[y][x] where y is the colatitude of a point on the
  334.    surface and takes values from 0 to rho-1, and x is the  longitude  of  a
  335.    point  on  the surface and takes values from 0 to 2*rho-1. Note that the
  336.    skin wraps around in the x sense (so x=2*rho is equivalent to x=0),  but
  337.    not  in  the  y sense; y=0 marks the north pole, y=rho-1 marks the south
  338.    pole.
  339.  
  340.         The elements in this array could be floating-point numbers, but, to
  341.    speed  up  calculations, it is probably best to use integers. All of the
  342.    altitude values are initially set to zero,  or  some  other  appropriate
  343.    base value.
  344.  
  345.  
  346.    3.3.  The Riffs
  347.  
  348.         To generate the planet's surface, the Skin must be treated as if it
  349.    is  spherical  when it is, in fact, flat. This is achieved with the help
  350.    of a set of data arrays, called the Riffs, which describe the shapes  of
  351.    the  possible  fractures  that can be made in the surface. The Riffs are
  352.    unchanging; the random planet surfaces (for fixed rho) are  all  created
  353.    using the same set of Riffs.
  354.  
  355.         There are rho/2 Riffs, numbered from 0 to rho/2-1, each  consisting
  356.    of  rho/2  (again  0  to  rho/2-1)  integer  values  between 0 and rho/2
  357.    inclusive.  Let the Riffs be represented by riff[A][t] where  A  is  the
  358.    Riff number and t is the position within that Riff.
  359.  
  360.         The Riffs are generated in the following way  with  A  and  t  both
  361.    taking values 0 to rho/2-1:
  362.  
  363.                         omega = ( A + 0.5 ) * pi / rho
  364.                         sigma = ( t + 0.5 ) * pi / rho
  365.                    gamma = atan( tan(omega) * cos(sigma) )
  366.                 riff[A][t] = rho/2 - round( gamma * rho / pi )
  367.  
  368.                    [round() means 'to the nearest integer'.
  369.                        atan() means 'inverse tangent'.]
  370.  
  371.         It can take a little time to create all the Riffs, so it is  not  a
  372.    bad idea to save them to disk for future use.
  373.  
  374.  
  375.    3.4.  The Song
  376.  
  377.         This section  describes  the  actual  routine  that  generates  the
  378.    planet's surface. It is fairly complicated.
  379.  
  380.         For this part of the program the Skin (with all  entries  initially
  381.    set to zero), the Riffs, and the constant rho are required.  A couple of
  382.    new constants also need to be defined.
  383.  
  384.    FULL_BLAST  This value determines the size of the fractures made in  the
  385.                surface; it is the actual size of the first fracture made.
  386.  
  387.    POWER       This  constant  controls  the  style  of  formation  of  the
  388.                surface.  A value of 1 results in mostly straight coastlines
  389.                whilst a higher value gives more jagged shapes. Try a  value
  390.                of 3, 4 or 5.
  391.  
  392.         The number of 'strikes' used to make the  surface  (and  hence  the
  393.    time  taken)  is equal to FULL_BLAST to the power of POWER. Something in
  394.    the order of several thousand strikes I find are needed  to  generate  a
  395.    convincing surface.
  396.  
  397.         The procedure that follows is written in an vague approximation  of
  398.    the C language.
  399.  
  400.         FULL_BLAST = 20.0
  401.         POWER = 3.0
  402.  
  403.         int skin[rho][2*rho];  /* Using integer altitude values */
  404.         int riff[rho/2][rho/2];  /* Riff data already generated */
  405.  
  406.         int strike;  /* Number of fractures to make */
  407.         int displac = 0;  /* Move everything upwards; rebalance later */
  408.  
  409.         int blast;  /* Depth of next fracture */
  410.         int rf_num;  /* Riff to use on this fracture */
  411.         int r1;  /* Preliminary random choice of riff */
  412.         int half;  /* Random choice of which half of surface to hit */
  413.         int dirn;  /* Random choice of whether to blast up or down */
  414.         int x_start;  /* Position around equator to start at */
  415.         float r2, dis;  /* Weight riff probabilities */
  416.  
  417.         int x, y;  /* Every program needs some x and y coordinates */
  418.         int x1, x2, x3, x4;  /* A few spare x coordinates */
  419.  
  420.  
  421.         /* 'strike' is initially FULL_BLAST to the power of POWER */
  422.         /* We repeat until strike (decremented by one each time) is zero */
  423.         for ( strike = pow( FULL_BLAST, POWER ) ; strike > 0 ; strike-- ) {
  424.  
  425.             /* pow(A,B) means A to the power of B (A^B or A**B) */
  426.             /* Watch out for conversions between 'int' and 'float' here */
  427.             blast = FULL_BLAST / pow( strike, 1.0/POWER );
  428.  
  429.             /* Choose random values for next fracture */
  430.             r1 = random(rho/2);  /* int from 0 to rho/2-1 (inclusive) */
  431.             x_start = random(2*rho);  /* 0 to 2*rho-1 (inclusive) */
  432.             half = random(2);  /* 0 or 1 */
  433.             dirn = random(2);  /* 0 or 1 */
  434.             r2 = ((float)rand())/(float)RAND_MAX;  /* between 0 and 1 */
  435.  
  436.             /* Balance choice of riffs evenly over the surface */
  437.             /* (Avoids most fractures being along the equator) */
  438.             dis = sin( pi * (r1+0.5) / rho );  /* All in floating-point */
  439.             if ( r2 <= dis*dis ) rf_num = r1;
  440.             else rf_num = rho/2 - 1 - r1;
  441.  
  442.             /* We only displace in the northern half of the planet */
  443.             /* then make up for it at the end using 'displac' */
  444.             if ( dirn == 0 ) blast = -blast;
  445.             if ( half == 0 ) displac = displac - blast;
  446.  
  447.             /* This loop would be best converted to assembly language */
  448.             /* The loop goes from x equal to 0 to x equal to rho/2-1 */
  449.             /* Note: A%B means the remainder of A divided by B (A mod B) */
  450.             /*       A+=B means A=A+B */
  451.             for ( x = 0 ; x < rho/2 ; x++ ) {
  452.                 x1 = ( x_start + x ) % (2*rho);
  453.                 x2 = ( x_start + 2*rho-1 - x ) % (2*rho);
  454.                 for ( y = 0 ; y < riff[rf_num][x] ; y++ ) {
  455.                 /* If riff[rf_num][x] is zero this loop is not executed */
  456.                     skin[y][x1] += blast;
  457.                     skin[y][x2] += blast;
  458.                 }
  459.                 x3 = ( x_start + rho + x ) % (2*rho);
  460.                 x4 = ( x_start + rho-1 - x ) % (2*rho);
  461.                 for ( y = 0 ; y < rho-1 - riff[rf_num][x] ; y++ ) {
  462.                 /* If riff[rf_num][x] is rho-1 this loop is not executed */
  463.                     skin[y][x3] += blast;
  464.                     skin[y][x4] += blast;
  465.                 }
  466.             }
  467.         }
  468.  
  469.         /* Finally, shift all the altitudes to rebalance the zero level */
  470.         /* (This is not strictly necessary since altitudes are relative) */
  471.         for( y = 0 ; y < rho ; y++ ) for( x = 0 ; x < 2*rho ; x++ )
  472.             skin[y][x]+=displac;
  473.  
  474.  
  475.         This routine assumes that the Skin is made up of integer values and
  476.    so  uses  mostly  integer variables. The advantages of using ints rather
  477.    than floats are that they use less memory, they operate faster, and they
  478.    allow the program to be more easily converted into assembly language.
  479.  
  480.         Bear in mind that integer variables  can  'overflow'  if  they  are
  481.    required  to  store very large numbers. If the program begins to produce
  482.    unexpected results, this could be the cause.
  483.  
  484.  
  485.    3.5.  The Complexion
  486.  
  487.         The Complexion is an array of the same size and shape as  the  Skin
  488.    and  is  referred  to  by  xion[y][x]. Each entry in the Complexion is a
  489.    colour value and the array as a whole forms a  coloured-in  map  of  the
  490.    planet's surface.
  491.  
  492.         The Complexion is derived from the Skin by assigning colours  based
  493.    on the altitude at each point. A simple way to do this is as follows.
  494.  
  495.         Choose a set of K colours such that colour 1 represents the  lowest
  496.    altitude and colour K represents the highest altitude.  Look at the Skin
  497.    to find the maximum and minimum altitude values and divide up the  range
  498.    of  altitudes into K equal-sized groups, each corresponding to a colour.
  499.    Then, if the maximum and minimum altitudes are the values max and min,
  500.  
  501.         xion[y][x] = int{ ( skin[y][x] - min ) * K / (max-min+1) } + 1
  502.  
  503.         For a very simple planet design, colour any point with an  altitude
  504.    of (max+min)/2 or less sea-blue, and any other point grass-green.
  505.  
  506.  
  507.    4.  Notes on Implementation
  508.  
  509.  
  510.    4.1.  Attachment to Hardware
  511.  
  512.         The details of writing this program depend greatly on the  hardware
  513.    being  used: the CPU instruction set, the available memory, the graphics
  514.    specifications and the computer's speed, among other things.
  515.  
  516.         In this section I'll sketch out how I envisage this  program  being
  517.    implemented to make the most of the hardware available.
  518.  
  519.  
  520.    4.2.  Choice of Screen Mode
  521.  
  522.         The way in which the screen memory of  the  computer  is  organised
  523.    determines to a large extent the structure of the animation routine.
  524.  
  525.         The simplest way to write to the screen would be to use a  function
  526.    of  the  type  plot(x_pos, y_pos, colour), but this would give painfully
  527.    slow animation. It is usually much better to write  directly  to  screen
  528.    memory.
  529.  
  530.         The nicest screen mode for this sort of animation would  have  each
  531.    pixel  controlled  by  one byte of screen memory (suggesting a mode with
  532.    256 colours) and would have consecutive pixels along a  horizontal  line
  533.    corresponding  to consecutive bytes in memory, with the horizontal lines
  534.    arranged in order top to bottom. With other kinds of screen  arrangement
  535.    animation routines become a little more tricky to put together.
  536.  
  537.         Some screen modes use pixels which aren't exact squares,  and  this
  538.    results  in  planets  that  are  elliptical  instead  of  circular.   To
  539.    compensate for this the (xs,ys) coordinates used in the Square Gush have
  540.    to be rescaled (the result being, in effect, a Rectangular Gush).
  541.  
  542.         The choice of screen resolution is a balancing of two factors:  for
  543.    planets  of a fixed size, a low-resolution display gives a less detailed
  544.    image whilst  a  high-resolution  display  means  slower  animation.   A
  545.    resolution  of about 300 pixels by 200 pixels with a planet radius of 50
  546.    pixels (which is to say RADIUS=50) is a reasonable place to start.
  547.  
  548.  
  549.    4.3.  Gush Structure
  550.  
  551.         The amount of memory needed for  the  Gush  depends  on  a  lot  of
  552.    factors;  anything from two to ten bytes may be needed for each point to
  553.    be plotted, the number of which  will  be  around  PI*RADUIS*RADIUS.   A
  554.    straightforward  Line  Gush  for a planet with RADIUS=50 and rho=128 may
  555.    take up around 20k. A Compiled Gush for the  same  planet  may  take  up
  556.    closer to 60k.
  557.  
  558.         The value DIST has only a very slight effect on the  appearance  of
  559.    the planet. I usually give it a value somewhere in the thousands. It can
  560.    alternatively be omitted altogether: change  the  Gush  calculations  so
  561.    that  g=RADIUS  and  k=1.  This  is equivalent to making DIST infinitely
  562.    large (and has the advantage of speeding up the Gush calculations.)
  563.  
  564.         As default values  for  phi  and  theta,  try  45  and  30  degrees
  565.    respectively.
  566.  
  567.  
  568.    4.4.  Complexion Structure
  569.  
  570.         The resolution of the planet's surface,  rho,  should  be  given  a
  571.    value  in  compromise  of  two  factors:  if rho is not large enough the
  572.    planet's surface will be too blocky, but  as  rho  becomes  larger  more
  573.    memory  is  required  for  the  Complexion  and  more  time is needed to
  574.    generate a planet surface. Also,  very  large  values  of  rho  give  no
  575.    obvious improvement to the appearance of the planet.
  576.  
  577.         In addition, the value of rho determines the  number  of  different
  578.    frames  seen  when  the  planet  is animated; the number of positions of
  579.    rotation the planet can be viewed in is equal to 2*rho.
  580.  
  581.         In general, rho can be given any value, but a value of 128 may give
  582.    certain  advantages. Since the array xion[y][x] uses values of x between
  583.    0 and 2*rho-1 and values of y between 0 and rho-1,  rho=128  means  that
  584.    both  x  and  y  values  are byte-sized.  Also, the offset into the xion
  585.    array is 2*rho*y+x which becomes 256*y+x, so x and y  are  now  the  low
  586.    order and high order bytes of a CPU-friendly 16-bit word.
  587.  
  588.         Each point in the Complexion is a colour value; if no more than 256
  589.    colours  are  used,  then each entry in the array will take up one byte.
  590.    The total amount of memory used will then be 2*rho*rho bytes.
  591.  
  592.  
  593.    5.  Magrathea in Scattered Jumps: Plotting a Planet
  594.  
  595.  
  596.    5.1.  Animation Routines
  597.  
  598.         And so, at last, we  arrive  at  the  heart  of  the  program:  the
  599.    animation routine.
  600.  
  601.         What I'm going to do in this section is describe a  progression  of
  602.    methods  for  displaying  planets, starting with simple routines to help
  603.    explain the process and then moving on to more effective methods.
  604.  
  605.         Since the speed of the display routine is critical it  will  almost
  606.    certainly  need  to  be written in assembly language. With this in mind,
  607.    the descriptions of the routines are in a fairly low-level pseudo-code.
  608.  
  609.         Four bits of data are needed by  the  routines  when  displaying  a
  610.    planet:
  611.  
  612.    xion  The routine must have access to some  Complexion  data;  the  name
  613.          xion  will be used to mean, depending upon the routine, either the
  614.          entire array xion[y][x] or the  address  in  memory  of  (i.e.,  a
  615.          pointer to) the first byte in the array.
  616.  
  617.    gush  The routine needs Gush data of some description.  Like xion,  gush
  618.          will  refer  to either an array or a memory address as the routine
  619.          requires.
  620.  
  621.    scr   This value determines where on the screen the planet is displayed.
  622.          It  could  be  either  the  x  and y coordinates of a point on the
  623.          screen (referred to as scr.x and scr.y), or the address of a point
  624.          in  screen  memory.   The  point  in question will be the top-left
  625.          'corner' of the planet.
  626.  
  627.    rot   This is the state of rotation of the planet, the degree  to  which
  628.          it  appears  rotated  about  its  axis  when  drawn. It is a value
  629.          between 0 and 2*rho-1 with wrap-around meaning that  rot=2*rho  is
  630.          equivalent to rot=0.
  631.  
  632.  
  633.    5.2.  Using the Square Gush
  634.  
  635.         This is the simplest of the display methods;  it  uses  the  Square
  636.    Gush  as  input, treating it as an array, and plots points to the screen
  637.    using a plot function.  This method could be implemented in a high-level
  638.    language  to test how well the program works before going to the trouble
  639.    of writing a faster assembly language routine.
  640.  
  641.         In addition to the parameters which are passed to the routine  (see
  642.    5.1),  five local variables are used here: ys and xs are the coordinates
  643.    of a point in the Gush and on the screen;  col  is  the  colour  of  the
  644.    current point; xm and ym are values taken from the Gush data which refer
  645.    to a point in the Complexion.
  646.  
  647.         Remember that the Square Gush includes a number of  'null'  entries
  648.    for which both xm_val and ym_val are taken here to be equal to -1.
  649.  
  650.         ys = 0
  651.         L1:
  652.         xs = 0
  653.         L2:
  654.  
  655.         xm = gush[ys][xs].xm_val
  656.         ym = gush[ys][xs].ym_val
  657.         if ( xm = -1 ) and ( ym = -1 ) jump to L3
  658.         xm = ( xm + rot ) % (2*rho)
  659.         col = xion[ym][xm]
  660.         plot( (scr.x + xs), (scr.y + ys), col )
  661.  
  662.         L3:
  663.         xs += 1
  664.         if ( xs != 2*RADIUS ) jump to L2
  665.         ys += 1
  666.         if ( ys != 2*RADIUS ) jump to L1
  667.  
  668.  
  669.         It should be obvious roughly what this routine does. For each point
  670.    in  an  area  of  the  screen,  xm  and  ym  values  are  taken from the
  671.    corresponding point in the Square Gush.  If the point  is  valid  (i.e.,
  672.    part  of  the planet) then a colour value is derived from the Complexion
  673.    and the point is plotted.
  674.  
  675.         The value rot is added to xm, the result being trimmed so  that  it
  676.    remains  between  0  and  2*rho-1.  In this way, the planet's surface is
  677.    effectively made to rotate around the planet. (This  is  the  only  time
  678.    that rot is used in the program.)
  679.  
  680.         (Incidentally, 'A != B' means 'A not equal to B' and 'A % B'  means
  681.    'remainder of A divided by B'.)
  682.  
  683.  
  684.    5.3.  Using the Line Gush
  685.  
  686.         Because the Line Gush does not include the 'null'  entries  of  the
  687.    Square  Gush,  a  routine  using  it  can run faster. The Line Gush is a
  688.    sequence of values in memory which are accessed in order.  The  notation
  689.    x=[gush++]  will be used to mean 'load the next Gush value into x' - the
  690.    variable/register gush holds the address of the next value to  be  used;
  691.    this  value  is  loaded into x and gush is incremented by an appropriate
  692.    amount.
  693.  
  694.         The format of the Line Gush is defined  by  the  structure  of  the
  695.    routine.   In this example, the first value is the total number of lines
  696.    to draw and is followed by the appropriate data for each line consisting
  697.    of:
  698.  
  699.    (1)  An amount to add to or subtract from scr.x, the x-coordinate of the
  700.         next  point  to  be  plotted, so that this line is drawn to fit the
  701.         circular shape of the planet.
  702.  
  703.    (2)  The number of points to be plotted on this line.
  704.  
  705.    (3)  For each point on this line, a value for xm followed by a value for
  706.         ym.
  707.  
  708.         The local variables used in this routine are the same as those used
  709.    in  Section  5.2  except that xs and ys are renamed xcount and ycount to
  710.    reflect their slightly different application.
  711.  
  712.         ycount = [gush++]
  713.         L1:
  714.         scr.x += [gush++]
  715.         xcount = [gush++]
  716.         L2:
  717.  
  718.         xm = [gush++]
  719.         ym = [gush++]
  720.         xm = ( xm + rot ) % (2*rho)
  721.         col = xion[ym][xm]
  722.         plot( scr.x, scr.y, col )
  723.         scr.x += 1
  724.  
  725.         xcount -= 1
  726.         if ( xcount != 0 ) jump to L2
  727.  
  728.         scr.y += 1
  729.         ycount -= 1
  730.         if ( ycount != 0 ) jump to L1
  731.  
  732.  
  733.         Note that the value of RADIUS is no longer needed here  -  all  the
  734.    required data is stored in the Gush.
  735.  
  736.  
  737.    5.4.  Screen Memory Addresses
  738.  
  739.         The most obvious way  of  increasing  the  speed  of  the  previous
  740.    routine  is  to  avoid  using  the  plot  function  and to instead write
  741.    directly to the screen memory.
  742.  
  743.         Section 4.2 lists the assumptions I am making about the  layout  of
  744.    the  screen  memory.  The  variable/register named scr will now hold the
  745.    memory address of the next point of  the  screen  to  be  altered.   The
  746.    notation  [scr]=x  will  be used to mean that the byte or word at screen
  747.    memory address scr is to be loaded with the value x.
  748.  
  749.         Apart from scr, the only other difference between this routine  and
  750.    the  last  one  is in the Line Gush: the value which before was added to
  751.    scr.x is now added to scr. This new value is the difference between  the
  752.    address  of  the  last  point on the previous line of the planet and the
  753.    address of the first point on the new line of the planet.
  754.  
  755.         ycount = [gush++]
  756.         L1:
  757.         scr += [gush++]
  758.         xcount = [gush++]
  759.         L2:
  760.  
  761.         xm = [gush++]
  762.         ym = [gush++]
  763.         xm = ( xm + rot ) % (2*rho)
  764.         col = xion[ym][xm]
  765.         [scr] = col
  766.         scr += 1
  767.  
  768.         xcount -= 1
  769.         if ( xcount != 0 ) jump to L2
  770.         ycount -= 1
  771.         if ( ycount != 0 ) jump to L1
  772.  
  773.  
  774.    5.5.  Games with Xion
  775.  
  776.         All of the routines so far have treated xion as an array; in a low-
  777.    level language the address in memory of a value is needed instead of its
  778.    position in the array, so we replace the line
  779.  
  780.                               col = xion[ym][xm]
  781.  
  782.  
  783.    with the line
  784.  
  785.                         col = [ xion + 2*rho*ym + xm ]
  786.  
  787.  
  788.         (This assumes that one byte is used for each of the values  in  the
  789.    Complexion.)
  790.  
  791.  
  792.    5.5.1.
  793.  
  794.         The use of xm and ym to  point  to  values  in  the  Complexion  is
  795.    somewhat  inefficient.  One fairly simple way of speeding the process up
  796.    would be to store the value of 2*rho*ym or  even  xion+2*rho*ym  in  the
  797.    Gush instead of just ym.  The modulus ('%') calculation can also be made
  798.    faster by choosing a value for rho that is a  power  of  two;  then  the
  799.    modulus  operation can be replaced by a logical AND operation which most
  800.    microprocessors can perform much  faster.  If  this  is  done  then  the
  801.    central loop of the routine will look something like:
  802.  
  803.         xm = [gush++]
  804.         ym2 = [gush++]
  805.         xm = ( xm + rot ) AND (2*rho-1)
  806.         col = [ ym2 + xm ]
  807.         [scr] = col
  808.         scr += 1
  809.  
  810.  
  811.  
  812.    5.5.2.
  813.  
  814.         An alternative approach can be used on CPUs which allow  their  16-
  815.    or  32-bit registers to be split into component 8-bit registers. Suppose
  816.    one complete register is called yyxx and that its lowest 8 bits  can  be
  817.    operated  upon  separately  as the register xx.  Since xx is only 8 bits
  818.    long, the equivalent of an AND 255 operation is automatically  performed
  819.    on it whenever it is used.  Thus if rho is set equal to 128 and the Gush
  820.    holds the combined value 2*rho*ym+xm instead of both xm and ym, then the
  821.    core of the routine is simply:
  822.  
  823.         yyxx = [gush++]
  824.         xx += rot
  825.         col = [ xion + yyxx ]
  826.         [scr] = col
  827.         scr += 1
  828.  
  829.  
  830.    5.5.3.
  831.  
  832.         There is yet a  third  way  to  simplify  the  routine,  though  it
  833.    requires  twice  as  much  memory  for the Complexion data.  An expanded
  834.    Complexion is created in which each line of the map appears twice:  Line
  835.    1 is followed in memory by another copy of Line 1 before getting to Line
  836.    2, such that [xion] is the same as [xion+2*rho], [xion+1] is the same as
  837.    [xion+2*rho+1],  and  so  on up until [xion+4*rho] which is the start of
  838.    the second line.
  839.  
  840.         An expanded Complexion removes  the  need  for  the  modulus  ('%')
  841.    calculation when rot is added on to xm.
  842.  
  843.         Instead of the two values xm and ym being used for  each  point  in
  844.    the  Line  Gush, the single combined value xion+4*rho*ym+xm (or possibly
  845.    just 4*rho*ym+xm) is used. The single  variable/register  ymxm  replaces
  846.    both xm and ym and the core of the routine now becomes:
  847.  
  848.         ymxm = [gush++]
  849.         col = [ ymxm + rot ]    ( or [ xion + ymxm + rot ]  )
  850.         [scr] = col
  851.         scr += 1
  852.  
  853.  
  854.         (Note that 4*rho now replaces 2*rho as the width of the  Complexion
  855.    array.)
  856.  
  857.  
  858.    5.6.  Scattered Jumps
  859.  
  860.         There is another aspect of the display routine which can  still  be
  861.    made  more  efficient:  the  looping  mechanism.  For every point of the
  862.    planet that is plotted, additional instructions must be executed to loop
  863.    back to plot the next point. These time wasting loop instructions can be
  864.    missed out by repeating the plotting code in memory once for each of the
  865.    points to be plotted.
  866.  
  867.         The problem with this is that the number of points to be plotted on
  868.    each  line varies. The solution is to miss out some of the plot routines
  869.    by jumping past them - this needs an 'indirect'  jump  instruction,  the
  870.    address to jump to being stored in a register or in memory.
  871.  
  872.         The following code shows how this method could be implemented  into
  873.    a  display  routine.  It  is  based  around  an  expanded Complexion, as
  874.    described in the previous sub-section. The value in the Gush which  used
  875.    to  be  the  number  of points to plot on this line is now an address to
  876.    jump to in order to plot that number of points.
  877.  
  878.         ycount = [gush++]
  879.  
  880.         L1:
  881.         scr += [gush++]
  882.         jump to [gush++]
  883.  
  884.         [...]
  885.  
  886.         * ymxm = [gush++]
  887.         * col = [ ymxm + rot ]
  888.         * [scr] = col
  889.         * scr += 1
  890.  
  891.         ycount -= 1
  892.         if ( ycount != 0 ) jump to L1
  893.  
  894.  
  895.         The code marked by the asterisks plots  one  point.  Assuming  that
  896.    MAX_R  is  the  largest  value  of  RADIUS ever likely to be used in the
  897.    program, then the asterisked code needs to be  repeated  2*MAX_R  times.
  898.    The complete routine should take up no more than a few kilobytes.
  899.  
  900.  
  901.    5.7.  The Compiled Gush
  902.  
  903.         This is a way by which the animation can be dramatically  increased
  904.    in  speed (and thanks to Jamie Lokier for pointing this out): instead of
  905.    the Gush containing numerical data to be read by an  animation  routine,
  906.    it can itself be an executable routine with the data hard-wired into it.
  907.  
  908.         The Square Gush is translated directly into machine  code,  with  a
  909.    few bytes of code for each of the points to be plotted. A section of the
  910.    Compiled Gush (working with an  expanded  Complexion)  would  then  look
  911.    something like:
  912.  
  913.         col = [ m1 + rot ]
  914.         [scr] = col
  915.         scr += 1
  916.  
  917.         col = [ m2 + rot ]
  918.         [scr] = col
  919.         scr += 1
  920.  
  921.         col = [ m3 + rot ]
  922.         [scr] = col
  923.         scr += 1
  924.  
  925.         [...]
  926.  
  927.  
  928.    where m1, m2 and m3 are immediate (constant) values.
  929.  
  930.         The Compiled Gush also contains machine instructions  to  move  scr
  931.    from  the  end  of  one line to the start of the next, and, of course, a
  932.    'return'  instruction  at  the  end.  (Furthermore,  quite  a   bit   of
  933.    optimization  can  often  be  done  on the code as it is generated.) The
  934.    planet is then animated simply by choosing values for scr  and  rot  and
  935.    calling the Compiled Gush code.
  936.  
  937.         But there is one big drawback to this: a Compiled Gush takes  up  a
  938.    lot  of memory, several times more than a normal Gush. Fast animation is
  939.    what this method delivers, but it does so at the cost of a  large  chunk
  940.    of memory.
  941.  
  942.  
  943.    6.  Finishing Off
  944.  
  945.  
  946.    6.1.  Putting Together the Pieces
  947.  
  948.         Way back at the beginning of all this  I  described  the  animation
  949.    program  as  consisting of three basic parts. The code for creating Gush
  950.    data is detailed in Section 2, the code for creating Complexion data  is
  951.    detailed  in  Section 3, and Section 5 constructs a routine that draws a
  952.    planet using these two data arrays.
  953.  
  954.         In Section 1.4, constant  values  (rho,  RADIUS,  DIST,  etc.)  are
  955.    introduced.  Whatever  values  these constants are given it is important
  956.    that they remain the same in all parts of the program.
  957.  
  958.         With all segments of the program complete an animation can  finally
  959.    be  put  together.  The simplest animation, that of a planet spinning in
  960.    the centre of the screen, is accomplished in the following way.
  961.  
  962.         Create a Gush and a Complexion, storing  both  in  files  on  disk.
  963.    Write  a  simple  piece  of code that first loads in both data files and
  964.    then, with an appropriate value for scr (the point on the  screen  where
  965.    the  planet  is  to  appear),  repeatedly calls the display routine. The
  966.    additional parameter rot is increased (or decreased) by a  small,  fixed
  967.    amount  on  each call with a modulus operation keeping it in the range 0
  968.    to 2*rho-1.
  969.  
  970.         There is  not  much  more  to  say  about  the  animation;  further
  971.    elaborations  are  left  to  the  programmer's  imagination.  As  a  few
  972.    suggestions:
  973.  
  974.    *   Store several different Gushes on disk and  select  one  to  use  at
  975.        random  when  the animation is run. The Gushes are all created using
  976.        different values for phi and theta (and possibly RADIUS).
  977.  
  978.    *   Use different sets of colours for different planets; choose the  set
  979.        to be used at random when converting a Skin to a Complexion.
  980.  
  981.    *   Make more interesting planet surfaces; add random cloud and mountain
  982.        generation,  or  edit a Complexion using a standard bitmap editor to
  983.        give it a smiling face.
  984.  
  985.    *   Draw a star field behind the planet (to  make  the  fact  that  it's
  986.        supposed to be a planet more obvious!). Animate the star field.
  987.  
  988.    *   Shade the planet (as described in the next sub-section).
  989.  
  990.    *   Before displaying the next frame of the planet, delete the old frame
  991.        and  change  the  value  of  scr  so  that  the  planet appears in a
  992.        different place. Make the planet move. Make it bounce!
  993.  
  994.  
  995.    6.2.  Illumination
  996.  
  997.         In Section 2.3, when calculating  values  for  the  Gush  a  vector
  998.    P=(xp,yp,zp)  is  generated  for  each point (xs,ys). This vector can be
  999.    used to give a basic illumination effect, that of light shining onto the
  1000.    planet from a fixed direction.
  1001.  
  1002.         For this effect to work, some method for selectively  altering  the
  1003.    brightness  of  the  pixels  making  up  the  planet's  image is needed.
  1004.    Ideally, each pixel should have a collection  of  bits  controlling  its
  1005.    brightness which are independent of the bits controlling its colour. The
  1006.    planet can then be shaded by adjusting just these brightness bits.
  1007.  
  1008.         Define a vector I=(xi,yi,zi) as  the  direction  from  which  light
  1009.    falls  onto the planet. Then, for each (xs,ys), calculate a value lambda
  1010.    as follows:
  1011.  
  1012.                       q = sqrt( xi*xi + yi*yi + zi*zi )
  1013.  
  1014.                      lambda =  ( I . P ) / ( |I| * |P| )
  1015.                    = ( xi*xp + yi*yp + zi*zp ) / ( q * g )
  1016.  
  1017.        (The calculation of the constant g is described in Section 2.3)
  1018.  
  1019.  
  1020.         lambda is a measure of the illumination of the  point  (xs,ys)  and
  1021.    takes  values  from  -1 (minimum brightness) to +1 (maximum brightness).
  1022.    (Strictly, if lambda's value is below zero, then no light at all reaches
  1023.    (xs,ys) - it is on the dark side of the planet.)
  1024.  
  1025.         Transform each lambda into a number kappa, the value to be  put  in
  1026.    the  brightness  bits  of the pixel at (xs,ys).  To avoid the shading of
  1027.    the planet being too regular, add  a  slight  random  influence  to  the
  1028.    transformation  such  as changing the value of lambda by a small, random
  1029.    amount. This blurs the border between 'light' and 'dark' regions of  the
  1030.    planet.
  1031.  
  1032.         Illuminating the planet is now a matter of keeping  the  brightness
  1033.    of  each  pixel  fixed  (at  the value kappa) whilst allowing the planet
  1034.    display routine to fill in the pixel's colour.
  1035.  
  1036.         One way of doing this would be to  set  each  pixel  to  brightness
  1037.    kappa  and  colour  zero,  and  then  use  a logical OR operation in the
  1038.    display routine to overlay colours onto the pixels. Before  drawing  the
  1039.    next  frame,  the  colour  of each pixel would need to be reset to zero,
  1040.    either by rewriting all brightness and colour  values  together,  or  by
  1041.    resetting just the colour bits using multiple logical AND operations.
  1042.  
  1043.  
  1044.    6.3.  Whys and Wherefores
  1045.  
  1046.         That's  about  it  really.  The  one  thing  left  to  add  is   an
  1047.    explanation.  I've  written  some seven thousand words describing how to
  1048.    put together a pretty but pointless animation. Why did I bother?
  1049.  
  1050.         Well, as I said earlier, nearly all of the mildly clever  animation
  1051.    techniques  used  here  I've had to work out for myself. They aren't the
  1052.    sort of thing that you commonly find  in  text  books.  In  a  way  this
  1053.    document  is a small attempt to change that - it's sort of a "Beginner's
  1054.    Guide to Real-time Animation (Volume 1: Things That Bounce)".
  1055.  
  1056.         Also, there's a limit to the number of things that I can  think  of
  1057.    to do to spinning planets. I want to see what ideas other people come up
  1058.    with using this as a starting point.
  1059.  
  1060.         So, having read this document, if  you're  interested  in  bringing
  1061.    this animation to life, go to it, and good luck to you.
  1062.  
  1063.  
  1064.    6.4.  Acknowledgements
  1065.  
  1066.         Thanks must go to Matt Spinner  for  helping  create  the  original
  1067.    planet demo and to everyone else who was around at the time (Dave, Nick,
  1068.    Simon, Phill, JT, etc).
  1069.  
  1070.         And thanks also to Jay Dresser and Jamie  Lokier  from  Usenet  for
  1071.    comments on the original posting.
  1072.  
  1073.  
  1074.  
  1075.